<script setup lang="ts">
/**
 * @desc DingDesigner
 * @author DragonTeam <https://www.bpmport.com>
 * @since 2024/10/28 10:36
 */

import { type ComponentInstance, type PropType, computed, nextTick, provide, ref, watchEffect } from 'vue'
import DingFlow from '@/components/ding-flow/DingFlow.vue'
import type {
  AppendMenuProvider,
  BaseNode,
  BaseNodeType,
  CanAppend,
  CanDropped,
  CanMove,
  CanRemove,
  ExecutionValidator,
  FlowDirection,
  PreBehavior,
  SubprocessNode,
} from '@/types'
import { setGlobalConfig } from '@/configuration/global-config'
import DingPanel from '@/components/ding-panel/DingPanel.vue'
import { appendMenuGetter } from '@/injection'
import { defaultAppendMenuProvider } from '@/configuration/providers'

defineOptions({ name: 'DingDesigner' })

const $props = defineProps({
  data: {
    type: Object as PropType<BaseNode>,
    default: () => null,
  },
  direction: {
    type: String as PropType<FlowDirection>,
    default: 'vertical',
    validator: (v: FlowDirection) => ['vertical', 'horizontal'].includes(v),
  },

  canRemove: {
    type: [Boolean, Function] as PropType<CanRemove>,
    default: undefined,
  },
  canAppend: {
    type: [Boolean, Function] as PropType<CanAppend>,
    default: undefined,
  },
  canMove: {
    type: [Boolean, Function] as PropType<CanMove>,
    default: undefined,
  },
  canDropped: {
    type: [Boolean, Function] as PropType<CanDropped>,
    default: undefined,
  },

  removeValidator: {
    type: Function as PropType<ExecutionValidator>,
    default: undefined,
  },
  completenessValidator: {
    type: Function as PropType<ExecutionValidator>,
    default: undefined,
  },

  appendMenuProvider: {
    type: Function as PropType<AppendMenuProvider<BaseNodeType>>,
    default: defaultAppendMenuProvider,
  },

  preBehaviorOfDelete: {
    type: Function as PropType<PreBehavior>,
    default: undefined,
  },
})
const doEmit = defineEmits([
  'update:data',
  'zoom-changed',
  'node-click',
  'node-dblclick',
  'node-hover',
  'node-contextmenu',
])

const dir = ref<FlowDirection>($props.direction)
const dingFlowRef = ref<ComponentInstance<typeof DingFlow>>()
const dingPanelRef = ref<ComponentInstance<typeof DingPanel>>()
const activeNode = ref<BaseNode>()

const processData = computed<BaseNode>({
  get: () => $props.data,
  set: data => doEmit('update:data', data),
})

watchEffect(() => {
  setGlobalConfig('canAppend', $props.canAppend)
  setGlobalConfig('canRemove', $props.canRemove)
  setGlobalConfig('canMove', $props.canMove)
  setGlobalConfig('canDropped', $props.canDropped)
  setGlobalConfig('removeValidator', $props.removeValidator)
  setGlobalConfig('completenessValidator', $props.completenessValidator)
  setGlobalConfig('preBehaviorOfDelete', $props.preBehaviorOfDelete)
})
provide(appendMenuGetter, $props.appendMenuProvider)

// 切换方向
function toggleDirection() {
  dir.value = dir.value === 'vertical' ? 'horizontal' : 'vertical'
}
// 切换属性面板
function togglePanel() {
  dingPanelRef.value?.togglePanel()
}
// 切换根节点
function toggleRoot(node?: BaseNode) {
  dingFlowRef.value?.toggleRoot(node as SubprocessNode)
}
// 点击事件
function handleClick(node: BaseNode) {
  activeNode.value = node
  doEmit('node-click', node)
  nextTick(() => dingPanelRef.value?.togglePanel(true))
}
// 自适应布局
function fitViewport(padding?: number) {
  dingFlowRef.value?.fitViewport(padding)
}

defineExpose({
  toggleDirection,
  toggleRoot,
  togglePanel,
  fitViewport,
})
</script>

<template>
  <a-config-provider size="mini">
    <div class="ding-designer">
      <DingFlow
        ref="dingFlowRef"
        v-model:data="processData"
        :direction="dir"
        @node-click="handleClick"
        @zoom-changed="doEmit('zoom-changed', $event)"
        @node-dblclick="doEmit('node-dblclick', $event)"
        @node-hover="doEmit('node-hover', $event)"
        @node-contextmenu="doEmit('node-contextmenu', $event)"
      />
      <DingPanel ref="dingPanelRef" v-model:node="activeNode" />
    </div>
  </a-config-provider>
</template>

<style scoped lang="scss">
.ding-designer {
  width: 100%;
  height: 100%;
  overflow: hidden;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  background-color: var(--color-bg-1);
  color: var(--color-text-1);
}
</style>
